<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>The source code</title>
  <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
  <script type="text/javascript" src="../prettify/prettify.js"></script>
  <style type="text/css">
    .highlight { display: block; background-color: #ddd; }
  </style>
  <script type="text/javascript">
    function highlight() {
      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
    }
  </script>
</head>
<body onload="prettyPrint(); highlight();">
  <pre class="prettyprint lang-js"><span id='Ext-fx-Animator'>/**
</span> * @class Ext.fx.Animator
 * Animation instance

This class is used to run keyframe based animations, which follows the CSS3 based animation structure. 
Keyframe animations differ from typical from/to animations in that they offer the ability to specify values 
at various points throughout the animation.

__Using Keyframes__
The {@link #keyframes} option is the most important part of specifying an animation when using this 
class. A key frame is a point in a particular animation. We represent this as a percentage of the
total animation duration. At each key frame, we can specify the target values at that time. Note that
you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
event that fires after each key frame is reached.

__Example Usage__
In the example below, we modify the values of the element at each fifth throughout the animation.

    Ext.create('Ext.fx.Animator', {
        target: Ext.getBody().createChild({
            style: {
                width: '100px',
                height: '100px',
                'background-color': 'red'
            }
        }),
        duration: 10000, // 10 seconds
        keyframes: {
            0: {
                opacity: 1,
                backgroundColor: 'FF0000'
            },
            20: {
                x: 30,
                opacity: 0.5    
            },
            40: {
                x: 130,
                backgroundColor: '0000FF'    
            },
            60: {
                y: 80,
                opacity: 0.3    
            },
            80: {
                width: 200,
                y: 200    
            },
            100: {
                opacity: 1,
                backgroundColor: '00FF00'
            }
        }
    });

 * @markdown
 */
Ext.define('Ext.fx.Animator', {

    /* Begin Definitions */

    mixins: {
        observable: 'Ext.util.Observable'
    },

    requires: ['Ext.fx.Manager'],

    /* End Definitions */

    isAnimator: true,

<span id='Ext-fx-Animator-cfg-duration'>    /**
</span>     * @cfg {Number} duration
     * Time in milliseconds for the animation to last. Defaults to 250.
     */
    duration: 250,

<span id='Ext-fx-Animator-cfg-delay'>    /**
</span>     * @cfg {Number} delay
     * Time to delay before starting the animation. Defaults to 0.
     */
    delay: 0,

    /* private used to track a delayed starting time */
    delayStart: 0,

<span id='Ext-fx-Animator-cfg-dynamic'>    /**
</span>     * @cfg {Boolean} dynamic
     * Currently only for Component Animation: Only set a component's outer element size bypassing layouts.  Set to true to do full layouts for every frame of the animation.  Defaults to false.
     */
    dynamic: false,

<span id='Ext-fx-Animator-cfg-easing'>    /**
</span>     * @cfg {String} easing

This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
speed over its duration. 

- backIn
- backOut
- bounceIn
- bounceOut
- ease
- easeIn
- easeOut
- easeInOut
- elasticIn
- elasticOut
- cubic-bezier(x1, y1, x2, y2)

Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
be in the range [0, 1] or the definition is invalid.

[0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag

     * @markdown
     */
    easing: 'ease',

<span id='Ext-fx-Animator-property-running'>    /**
</span>     * Flag to determine if the animation has started
     * @property running
     * @type boolean
     */
    running: false,

<span id='Ext-fx-Animator-property-paused'>    /**
</span>     * Flag to determine if the animation is paused. Only set this to true if you need to
     * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
     * @property paused
     * @type boolean
     */
    paused: false,

<span id='Ext-fx-Animator-property-damper'>    /**
</span>     * @private
     */
    damper: 1,

<span id='Ext-fx-Animator-cfg-iterations'>    /**
</span>     * @cfg {Number} iterations
     * Number of times to execute the animation. Defaults to 1.
     */
    iterations: 1,

<span id='Ext-fx-Animator-property-currentIteration'>    /**
</span>     * Current iteration the animation is running.
     * @property currentIteration
     * @type int
     */
    currentIteration: 0,

<span id='Ext-fx-Animator-property-keyframeStep'>    /**
</span>     * Current keyframe step of the animation.
     * @property keyframeStep
     * @type Number
     */
    keyframeStep: 0,

<span id='Ext-fx-Animator-property-animKeyFramesRE'>    /**
</span>     * @private
     */
    animKeyFramesRE: /^(from|to|\d+%?)$/,

<span id='Ext-fx-Animator-cfg-target'>    /**
</span>     * @cfg {Ext.fx.target} target
     * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
     * method to apply the same animation to many targets.
     */

<span id='Ext-fx-Animator-cfg-keyframes'>     /**
</span>      * @cfg {Object} keyframes
      * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
      * is considered '100%'.&lt;b&gt;Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
      * &quot;from&quot; or &quot;to&quot;&lt;/b&gt;.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
      * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
      * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
 &lt;pre&gt;&lt;code&gt;
keyframes : {
    '0%': {
        left: 100
    },
    '40%': {
        left: 150
    },
    '60%': {
        left: 75
    },
    '100%': {
        left: 100
    }
}
 &lt;/code&gt;&lt;/pre&gt;
      */
    constructor: function(config) {
        var me = this;
        config = Ext.apply(me, config || {});
        me.config = config;
        me.id = Ext.id(null, 'ext-animator-');
        me.addEvents(
<span id='Ext-fx-Animator-event-beforeanimate'>            /**
</span>             * @event beforeanimate
             * Fires before the animation starts. A handler can return false to cancel the animation.
             * @param {Ext.fx.Animator} this
             */
            'beforeanimate',
<span id='Ext-fx-Animator-event-keyframe'>            /**
</span>              * @event keyframe
              * Fires at each keyframe.
              * @param {Ext.fx.Animator} this
              * @param {Number} keyframe step number
              */
            'keyframe',
<span id='Ext-fx-Animator-event-afteranimate'>            /**
</span>             * @event afteranimate
             * Fires when the animation is complete.
             * @param {Ext.fx.Animator} this
             * @param {Date} startTime
             */
            'afteranimate'
        );
        me.mixins.observable.constructor.call(me, config);
        me.timeline = [];
        me.createTimeline(me.keyframes);
        if (me.target) {
            me.applyAnimator(me.target);
            Ext.fx.Manager.addAnim(me);
        }
    },

<span id='Ext-fx-Animator-method-sorter'>    /**
</span>     * @private
     */
    sorter: function (a, b) {
        return a.pct - b.pct;
    },

<span id='Ext-fx-Animator-method-createTimeline'>    /**
</span>     * @private
     * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
     * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
     */
    createTimeline: function(keyframes) {
        var me = this,
            attrs = [],
            to = me.to || {},
            duration = me.duration,
            prevMs, ms, i, ln, pct, anim, nextAnim, attr;

        for (pct in keyframes) {
            if (keyframes.hasOwnProperty(pct) &amp;&amp; me.animKeyFramesRE.test(pct)) {
                attr = {attrs: Ext.apply(keyframes[pct], to)};
                // CSS3 spec allow for from/to to be specified.
                if (pct == &quot;from&quot;) {
                    pct = 0;
                }
                else if (pct == &quot;to&quot;) {
                    pct = 100;
                }
                // convert % values into integers
                attr.pct = parseInt(pct, 10);
                attrs.push(attr);
            }
        }
        // Sort by pct property
        Ext.Array.sort(attrs, me.sorter);
        // Only an end
        //if (attrs[0].pct) {
        //    attrs.unshift({pct: 0, attrs: element.attrs});
        //}

        ln = attrs.length;
        for (i = 0; i &lt; ln; i++) {
            prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
            ms = duration * (attrs[i].pct / 100);
            me.timeline.push({
                duration: ms - prevMs,
                attrs: attrs[i].attrs
            });
        }
    },

<span id='Ext-fx-Animator-property-applyAnimator'>    /**
</span>     * Applies animation to the Ext.fx.target
     * @private
     * @param target
     * @type string/object
     */
    applyAnimator: function(target) {
        var me = this,
            anims = [],
            timeline = me.timeline,
            reverse = me.reverse,
            ln = timeline.length,
            anim, easing, damper, initial, attrs, lastAttrs, i;

        if (me.fireEvent('beforeanimate', me) !== false) {
            for (i = 0; i &lt; ln; i++) {
                anim = timeline[i];
                attrs = anim.attrs;
                easing = attrs.easing || me.easing;
                damper = attrs.damper || me.damper;
                delete attrs.easing;
                delete attrs.damper;
                anim = Ext.create('Ext.fx.Anim', {
                    target: target,
                    easing: easing,
                    damper: damper,
                    duration: anim.duration,
                    paused: true,
                    to: attrs
                });
                anims.push(anim);
            }
            me.animations = anims;
            me.target = anim.target;
            for (i = 0; i &lt; ln - 1; i++) {
                anim = anims[i];
                anim.nextAnim = anims[i + 1];
                anim.on('afteranimate', function() {
                    this.nextAnim.paused = false;
                });
                anim.on('afteranimate', function() {
                    this.fireEvent('keyframe', this, ++this.keyframeStep);
                }, me);
            }
            anims[ln - 1].on('afteranimate', function() {
                this.lastFrame();
            }, me);
        }
    },

    /*
     * @private
     * Fires beforeanimate and sets the running flag.
     */
    start: function(startTime) {
        var me = this,
            delay = me.delay,
            delayStart = me.delayStart,
            delayDelta;
        if (delay) {
            if (!delayStart) {
                me.delayStart = startTime;
                return;
            }
            else {
                delayDelta = startTime - delayStart;
                if (delayDelta &lt; delay) {
                    return;
                }
                else {
                    // Compensate for frame delay;
                    startTime = new Date(delayStart.getTime() + delay);
                }
            }
        }
        if (me.fireEvent('beforeanimate', me) !== false) {
            me.startTime = startTime;
            me.running = true;
            me.animations[me.keyframeStep].paused = false;
        }
    },

    /*
     * @private
     * Perform lastFrame cleanup and handle iterations
     * @returns a hash of the new attributes.
     */
    lastFrame: function() {
        var me = this,
            iter = me.iterations,
            iterCount = me.currentIteration;

        iterCount++;
        if (iterCount &lt; iter) {
            me.startTime = new Date();
            me.currentIteration = iterCount;
            me.keyframeStep = 0;
            me.applyAnimator(me.target);
            me.animations[me.keyframeStep].paused = false;
        }
        else {
            me.currentIteration = 0;
            me.end();
        }
    },

    /*
     * Fire afteranimate event and end the animation. Usually called automatically when the
     * animation reaches its final frame, but can also be called manually to pre-emptively
     * stop and destroy the running animation.
     */
    end: function() {
        var me = this;
        me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
    }
});</pre>
</body>
</html>
